//@author A0106044W
package logic;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.logging.FileHandler;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.logging.SimpleFormatter;

import commandInfo.CommandHistory;
import commandInfo.CommandInfo;
import storage.DateRange;
import storage.Storage;
import storage.Task;
import storage.TimeRange;
import logic.EventTypeDeterminer.COMMAND_TYPE;
import flexiCommands.Clock;
import flexiCommands.FlexiCommandGenerator;
import flexiCommands.Command;
import storage.Date;

/**
 * 
 * 
 * 	       Sothearith Sreang
 *         All of the Logic part is written by me.
 * 		   Logic is created for handling user commands and giving outputs for GUI 
 */

public class Logic {
 
	private OperationHandler eventHandler;
	private Storage storage;
	private static ArrayList<Task> allTasks;
	private static ArrayList<Task> searchTasks;
	private ArrayList<Task> urgentTasks;
	private CommandHistory commandUndoHistory;
	private CommandHistory commandRedoHistory;
	private boolean isSearchCommand;
	
	public final static Logger logger = Logger.getLogger(Logic.class.getName());
	
	private static final String EMPTY_STRING = "";
	
	private static final String INDEX_OUT_OF_BOUND_ERROR_MESSAGE_PLURAL_TASK = "There are only %1d tasks in this program.";
	private static final String INDEX_OUT_OF_BOUND_ERROR_MESSAGE_SINGULAR_TASK = "There is only one task in this program.";
	private static final String INDEX_OUT_OF_BOUND_ERROR_MESSAGE_NO_TASK = "There are no tasks in this program.";
	private static final String EMPTY_COMMAND_HISTORY_MESSAGE = "There are currently no operations in this program's command history.";
	
	private enum FILTER_TYPE {ALL, PENDING, COMPLETE, OVERDUE};
	
	private static final String ALL_FILTER = "ALL";
	private static final String PENDING_FILTER = "PENDING";
	private static final String COMPLETE_FILTER = "COMPLETE";
	private static final String OVERDUE_FILTER = "OVERDUE";
	
	private static final boolean COMPLETE_STATUS = true;
	private static final boolean PENDING_STATUS = false;
	
	private static final boolean isDisplayTaskIndexMode = true;
	
	private static final boolean CLASHING_STATUS = true;

	// Constructor

	/********************************
	 ******************************** 
	 ********** Constructor *********
	 ******************************** 
	 ******************************** 
	 ********************************/

	public Logic() {
		initializeLog();
		initializeStorage();
		initializeCommandHistory();
		initliazeDisplayLists();
		initiazeIsSearchCommand();
	}

	// First level abstraction
	// These methods are called by Main.java

	/******************************************
	 ****************************************** 
	 ********** Public Access Methods *********
	 ****************************************** 
	 ******************************************/

	// Handles the user commands
	public String executeCommand(String inputCommand) {
		String feedback = EMPTY_STRING;

		try {
			Command userCommand = FlexiCommandGenerator
					.generateCommands(inputCommand);
			
			COMMAND_TYPE commandType = setUpCommandType(userCommand);
			
			updateIsSearchCommand(commandType);

			setUpEventHandler(userCommand, commandType);
			
			feedback = setUpOperation(commandType);
			
			updateCommandHistory(commandType);
			
			setUpAllTasksList();

		} catch (IndexOutOfBoundsException e) {
			feedback = handleIndexOutOfBoundsException();
		} catch (Exception e) {
			feedback = e.getMessage();
		}
		return feedback;
	}

	// handles the GUI display lists
	
	// This method is to send a list for "All" tab in GUI
	public DisplayTaskInfoList getAllTaskAttributesList(){
		setUpAllTasksList();
		DisplayTaskInfoList allTasksAttributeList = new DisplayTaskInfoList(allTasks);
		
		return allTasksAttributeList;
	}

	// This method is to send a list for "Search" tab in GUI
	public DisplayTaskInfoList getSearchTaskAttributesList(){
		if (searchTasks.isEmpty()) {
			return null;
		} else {
			setUpSearchTasksList();
			DisplayTaskInfoList searchTasksAttributeList = new DisplayTaskInfoList(searchTasks);
			
			return searchTasksAttributeList;
		}
	}
	
	// This method is to send a list for "Urgent" tab in GUI
	public DisplayTaskInfoList getUrgentTaskAttributesList() {
		setUpUrgentTasksList();
		DisplayTaskInfoList urgentTasksAttributeList = new DisplayTaskInfoList(urgentTasks);
			
		return urgentTasksAttributeList;
	}

	// This method is to set "Search" tab on when "display" command is executed
	public boolean isSearchTabOn() {
		return isSearchCommand;
	}
	
	public ArrayList<Integer> getClashingTasksIndexList() {
		setUpAllTasksList();
		ArrayList<Integer> clashingTasksIndexList = new ArrayList<Integer>();
		
		for(int index = 0; index < allTasks.size(); index++) {
			Task task = allTasks.get(index);
			if(task.getClashingStatus() == CLASHING_STATUS) {
				clashingTasksIndexList.add(index);
			}
		}
		
		return clashingTasksIndexList;
	}
	
	
	//Static method called by EventHandler
	
	public static void changeSearchTaskList(ArrayList<Task> taskList) {
		searchTasks = taskList;
	}
	
	public static int getSearchTaskSize() {
		return searchTasks.size();
	}
	
	public static ArrayList<Task> getDisplayTaskList() {
		return allTasks;
	}

	
	
	// Next level abstraction
	// These methods are called by first level abstraction methods

	private COMMAND_TYPE setUpCommandType(Command userCommand) {
		String stringCommandType = userCommand.getCommandType();
		COMMAND_TYPE commandType = EventTypeDeterminer
				.determineEventType(stringCommandType);

		return commandType;
	}
	
	private void setUpEventHandler(Command userCommand, COMMAND_TYPE commandType) throws Exception{
		switch (commandType) {
			case ADD_TASK:
				handleAddOperation(userCommand);
				break;
			case DELETE_TASK:
				handleRemoveOperation(userCommand);
				break;
			case EDIT_TASK:
				handleEditOperation(userCommand);
				break;
			case PENDING_TASK:
				handlePendingOperation(userCommand);
				break;
			case COMPLETE_TASK:
				handleCompleteOperation(userCommand);
				break;
			case DISPLAY:
				handleDisplay(userCommand);
				break;
			case UNDO:
				handleUndo();
				break;
			case REDO:
				handleRedo();
				break;
			case EXIT:
				handleExitOperation();
				break;
			case INVALID:
			default:
				handleInvalidCommandOperation(userCommand);
				break;
		}
	}
	
	private void updateIsSearchCommand(COMMAND_TYPE commandType) {
		if(commandType == COMMAND_TYPE.DISPLAY) {
			isSearchCommand = isDisplayTaskIndexMode;
		} else {
			isSearchCommand = ! isDisplayTaskIndexMode;
		}
	}

	private String setUpOperation(COMMAND_TYPE commandType) throws Exception {
		String feedback = EMPTY_STRING;
		
		if (commandType != COMMAND_TYPE.UNDO
				&& commandType != COMMAND_TYPE.REDO) {
			feedback = executeOperation();
		} else {
			feedback = undoOperation();
		}
		
		return feedback;
	}
	
	private void updateCommandHistory(COMMAND_TYPE commandType) {
		CommandInfo previousCommand = eventHandler.getCommandInfo();
		switch (commandType) {
			case ADD_TASK:
			case DELETE_TASK:
			case EDIT_TASK:
			case COMPLETE_TASK:
			case PENDING_TASK:
				commandUndoHistory.pushUserCommand(previousCommand);
				commandRedoHistory.clearHistory();
				break;
			case REDO:
				commandRedoHistory.popUserCommand();
				commandUndoHistory.pushUserCommand(previousCommand);
				break;
			case UNDO:
				commandUndoHistory.popUserCommand();
				commandRedoHistory.pushUserCommand(previousCommand);
				break;
			default:
				break;
		}

	}

	// initialize Event Handler from user command
	
	private void handleAddOperation(Command userCommand){
		Task task = setTask(userCommand);
		eventHandler = new AddHandler(task);
	}

	private void handleRemoveOperation(Command userCommand) throws Exception, IndexOutOfBoundsException {
		String filter = userCommand.getQuery();
		
		if(filter == null) {
			Task task = getTask(userCommand);
			eventHandler = new RemoveHandler(task);
		} else {
			ArrayList<Task> taskList = getTasksListByFilter(filter);
			eventHandler = new RemoveFilterHandler(taskList);
		}
	}

	private void handleEditOperation(Command userCommand) throws Exception, IndexOutOfBoundsException {
		Task task = getTask(userCommand);
		eventHandler = new EditHandler(userCommand, task);
	}

	private void handleCompleteOperation(Command userCommand) throws Exception, IndexOutOfBoundsException {
		String filter = userCommand.getQuery();
		
		if(filter == null) {
			Task task = getTask(userCommand);
			eventHandler = new CompleteHandler(task);
		} else {
			ArrayList<Task> taskList = getTasksListByFilter(filter);
			eventHandler = new CompleteFilterHandler(taskList);
		}
	}

	private void handlePendingOperation(Command userCommand) throws Exception, IndexOutOfBoundsException {
		String filter = userCommand.getQuery();
		
		if(filter == null) {
			Task task = getTask(userCommand);
			eventHandler = new PendingHandler(task);
		} else {
			ArrayList<Task> taskList = getTasksListByFilter(filter);
			eventHandler = new PendingFilterHandler(taskList);
		}
	}

	private void handleDisplay(Command userCommand){
		String query = userCommand.getQuery();
		eventHandler = new DisplayHandler(query);
	}

	private void handleExitOperation(){
		eventHandler = new ExitHandler();
	}

	private void handleInvalidCommandOperation(Command userCommand){
		String invalidCommand = userCommand.getCommandType();
		eventHandler = new InvalidCommandHandler(invalidCommand);
	}

	private void handleUndo() throws Exception {
		if (commandUndoHistory.isEmpty()) {
			throw new Exception(EMPTY_COMMAND_HISTORY_MESSAGE);
		} else {
			CommandInfo previousCommand = commandUndoHistory.getLatestUserCommand();
			COMMAND_TYPE previousCommandType = setUpCommandTypeForUndo(previousCommand);
			setUpUndoEventHandler(previousCommand, previousCommandType);
		}
	}

	private void handleRedo() throws Exception {
		if (commandRedoHistory.isEmpty()) {
			throw new Exception(EMPTY_COMMAND_HISTORY_MESSAGE);
		} else {
			CommandInfo previousCommand = commandRedoHistory.getLatestUserCommand();
			COMMAND_TYPE previousCommandType = setUpCommandTypeForUndo(previousCommand);
			setUpUndoEventHandler(previousCommand, previousCommandType);
		}
	}
	
	//
	
	
	// set up for handleUndoOperation and handleRedoOperation
	
	private COMMAND_TYPE setUpCommandTypeForUndo(CommandInfo previousCommand) {
		String previousCommandString = previousCommand.getCommand();
		return EventTypeDeterminer.determineEventType(previousCommandString);
	}
	
	private void setUpUndoEventHandler(CommandInfo previousCommand,
			COMMAND_TYPE previousCommandType){
		
		switch (previousCommandType) {
			case ADD_TASK:
				handleUndoAddOperation(previousCommand, previousCommandType);
				break;
				
			case DELETE_TASK:
				handleUndoRemoveOperation(previousCommand, previousCommandType);
				break;
				
			case EDIT_TASK:
				handleUndoEditOperation(previousCommand, previousCommandType);
				break;
				
			case PENDING_TASK:
				handleUndoPendingOperation(previousCommand, previousCommandType);
				break;
				
			case COMPLETE_TASK:
				handleUndoCompleteOperation(previousCommand, previousCommandType);
				break;
		
			default:
				handleUndoInvalidCommandOperation(previousCommand);
				break;
		}
	}
	
	
	// initialize EventHandler for undo and redo operations
	
	private void handleUndoCompleteOperation(CommandInfo previousCommand,
			COMMAND_TYPE previousCommandType) {
		boolean isFilter = previousCommand.isFilter();
		
		if(!isFilter) {
			eventHandler = new CompleteHandler(previousCommand);
		} else {
			eventHandler = new CompleteFilterHandler(previousCommand);
		}
	}

	private void handleUndoPendingOperation(CommandInfo previousCommand,
			COMMAND_TYPE previousCommandType) {
		boolean isFilter = previousCommand.isFilter();
		
		if(!isFilter) {
			eventHandler = new PendingHandler(previousCommand);
		} else {
			eventHandler = new PendingFilterHandler(previousCommand);
		}
		
	}

	private void handleUndoEditOperation(CommandInfo previousCommand,
			COMMAND_TYPE previousCommandType) {
		eventHandler = new EditHandler(previousCommand);
	}

	private void handleUndoAddOperation(CommandInfo previousCommand,
			COMMAND_TYPE previousCommandType) {
		boolean isFilter = previousCommand.isFilter();
		
		if(!isFilter) {
			eventHandler = new AddHandler(previousCommand);
		} else {
			eventHandler = new AddFilterHandler(previousCommand);
		}
		
	}
	
	private void handleUndoRemoveOperation(CommandInfo previousCommand,
			COMMAND_TYPE previousCommandType) {
		boolean isFilter = previousCommand.isFilter();
		
		if(!isFilter) {
			eventHandler = new RemoveHandler(previousCommand);
		} else {
			eventHandler = new RemoveFilterHandler(previousCommand);
		}
	}
	
	private void handleUndoInvalidCommandOperation(CommandInfo previousCommand) {
		String invalidCommand = previousCommand.getCommand();
		eventHandler = new InvalidCommandHandler(invalidCommand);
	}

	
	//

	
	// Type of operation execution

	private String executeOperation() throws Exception {
		return eventHandler.execute();
	}

	private String undoOperation(){
		return eventHandler.undo();
	}	

		
	//	set up allTasks, searchTasks, or UrgentTasks to be displayed on GUI
	
	private void setUpAllTasksList() {
		updateAllTasks();
		allTasks = setUpTasksList(allTasks);
	}

	private void setUpSearchTasksList(){
		updateSearchTasks();
		searchTasks = setUpTasksList(searchTasks);
	}

	private void setUpUrgentTasksList() {
		updateUrgentTasks();
		urgentTasks = setUpTasksList(urgentTasks);
	}

	private ArrayList<Task> setUpTasksList(ArrayList<Task> taskList) {
		ArrayList<Task> pendingTasksList = getPendingTaskList(taskList); 
		ArrayList<Task> completeTasksList = getCompleteTaskList(taskList);

		return setTaskList(pendingTasksList, completeTasksList);
	}
	
	private ArrayList<Task> getCompleteTaskList(ArrayList<Task> taskList) {
		return getStatusTasks(taskList, COMPLETE_STATUS);
	}

	private ArrayList<Task> getPendingTaskList(ArrayList<Task> taskList) {
		return getStatusTasks(taskList, PENDING_STATUS);
	}

	
	// update allTasks, searchTasks, or urgentTasks before displaying to GUI
	
	private void updateAllTasks() {
		allTasks.clear();
		
		for (Iterator<Task> tasks = storage.iterator(); tasks.hasNext();) {
			Task task = tasks.next();
			allTasks.add(task);
		}
	}
	
	private void updateSearchTasks() {
		if(allTasks.isEmpty()) {
			searchTasks.clear();
			return;
		}
		
		for(Task task: searchTasks) {
			if(!allTasks.contains(task)) {
				searchTasks.remove(task);
			}
		}
	}
	
	private void updateUrgentTasks() {
		urgentTasks.clear();
		
		Date currentDate = getCurrentDate();
		
		for (Iterator<Task> tasks = storage.iterator(); tasks.hasNext();) {
			Task task = tasks.next();
			Date date = task.getDateRange().getEndDate();
			if(date.compareTo(currentDate) == 0 && task.getDoneStatus() == PENDING_STATUS) {
				urgentTasks.add(task);
			}
		}		
	}
	

	// 

	
	// Set Access Methods
	
	private Task setTask(Command command) {
		String description = command.getDescription();
		assert description != null;
		DateRange date = command.getDateRange();
		assert date != null;
		TimeRange time = command.getTimeRange();
		assert time != null;

		
		Task task = null;
		try {
			task = new Task(description, date, time);
		} catch (Exception e) {
			logger.log(Level.SEVERE, e.getMessage());
		}

		return task;
	}
	
	private ArrayList<Task> setTaskList(
		ArrayList<Task> pendingTasksList, ArrayList<Task> completeTasksList) {
		
		ArrayList<Task> taskListClone = new ArrayList<Task>();
		for (Task t : pendingTasksList)
			taskListClone.add(t);
		for (Task t : completeTasksList)
			taskListClone.add(t);
		
		return taskListClone;
	}
	
	// Get Access Methods

	private Task getTask(Command command) throws IndexOutOfBoundsException {
		int displayIndex = command.getOption();
		Task taskClone = allTasks.get(displayIndex - 1);
		Task task = null;
		
		try {
			task = getTask(taskClone);
		} catch (Exception e) {
			logger.log(Level.SEVERE, "This task cannot be found in database while it can be found in display List.");
		}

		return task;
	}
	
	private Task getTask(Task taskDisplay) throws Exception {
		for (Iterator<Task> tasks = storage.iterator(); tasks.hasNext();) {
			Task task = tasks.next();
			if (task.compareTo(taskDisplay) == 0) {
				return task;
			}
		}

		throw new Exception("Task does not exist");
	}
	
	private ArrayList<Task> getTasksListByFilter(String filter) throws Exception{
		FILTER_TYPE filterType = getFilterType(filter);
		ArrayList<Task> filterTasksList = getTasksByFilterType(filterType);
		if(filterTasksList.isEmpty())
			throw new Exception(INDEX_OUT_OF_BOUND_ERROR_MESSAGE_NO_TASK);
		return filterTasksList;
	}

	private ArrayList<Task> getTasksByFilterType(FILTER_TYPE filterType) {
		switch(filterType) {
			case ALL:
				return getAllTasksList();
			case PENDING:
				return getAllPendingTasksList();
			case COMPLETE:
				return getAllCompleteTasksList();
			case OVERDUE:
				return getAllOverDueTasksList();
			default:
				return null;
		}
	}

	private ArrayList<Task> getAllTasksList() {
		ArrayList<Task> allTasksList = new ArrayList<Task>();
		
		for (Iterator<Task> tasks = storage.iterator(); tasks.hasNext();) {
			Task t = tasks.next();
			allTasksList.add(t);
		}
		
		return allTasksList;
	}
	
	private ArrayList<Task> getAllCompleteTasksList() {
		return getAllStatusTasks(COMPLETE_STATUS);
	}

	private ArrayList<Task> getAllPendingTasksList() {
		return getAllStatusTasks(PENDING_STATUS);
	}
	
	private ArrayList<Task> getAllOverDueTasksList() {
		ArrayList<Task> allOverDueTasksList = new ArrayList<Task>();
		
		for (Iterator<Task> tasks = storage.iterator(); tasks.hasNext();) {
			Task task = tasks.next();
			if(task.isOverdue()) {
				allOverDueTasksList.add(task);
			}
		}
		
		return allOverDueTasksList;

	}
	
	private ArrayList<Task> getAllStatusTasks(boolean status) {
		ArrayList<Task> allStatusTasksList = new ArrayList<Task> ();
		
		for (Iterator<Task> tasks = storage.iterator(); tasks.hasNext();) {
			Task task = tasks.next();
			if(task.getDoneStatus() == status) {
				allStatusTasksList.add(task);
			}
		}
		
		return allStatusTasksList;
	}
		
	private ArrayList<Task> getStatusTasks(ArrayList<Task> taskList, boolean status) {
		ArrayList<Task> statusTasksList = new ArrayList<Task>();
		
		for(Task task: taskList) {
			if(task.getDoneStatus() == status) {
				statusTasksList.add(task);
			}
		}
		
		return statusTasksList;
	}
	
	//
	
	private Date getCurrentDate() {
		Date currentDate = null;

		try{
			currentDate = Clock.getCurrentDate();
		} catch(Exception e) {
			logger.log(Level.SEVERE, e.getMessage());
		}
		
		return currentDate;
	}
	
	private FILTER_TYPE getFilterType(String filter) {
		FILTER_TYPE filterType = null;
		
		try{
			filterType = determineFilterType(filter);
		} catch (Exception e) {
			logger.log(Level.SEVERE, e.getMessage() + " Problems with flexiCommands class.");
		}
		
		return filterType;
	}
	
	//
	
	
	// initialize for Logic Constructor
	
	private void initiazeIsSearchCommand() {
		isSearchCommand = false;
	}

	private void initliazeDisplayLists() {
		allTasks = new ArrayList<Task>();
		searchTasks = new ArrayList<Task>();
		urgentTasks = new ArrayList<Task>();
	}

	private void initializeStorage() {
		try {
			storage = Storage.getInstance();
		} catch (NumberFormatException e) {
			logger.log(Level.SEVERE, "Problems with storage TreeSet. " + e.getMessage());
		} catch (Exception e) {
			logger.log(Level.SEVERE, "Problems with storage TreeSet." + e.getMessage());
		}
	}
	
	private void initializeCommandHistory() {
		commandUndoHistory = new CommandHistory();
		commandRedoHistory = new CommandHistory();
	}
	
	private void initializeLog() {
		FileHandler fileHandler = null;
		String logName = Logic.class.getName();
		String logFileName = logName + ".txt";
		
		try{
			fileHandler = new FileHandler(logFileName, false);
			fileHandler.setFormatter(new SimpleFormatter());
		} catch(SecurityException | IOException e) {
			 e.printStackTrace();
		}
		
		logger.addHandler(fileHandler);
		logger.setLevel(Level.OFF);
	}
	
	//

	
	// Helper methods
	
	private String handleIndexOutOfBoundsException() {
		String feedback = EMPTY_STRING;
		int size = storage.getSize();

		if (size == 0) {
			feedback = String.format(INDEX_OUT_OF_BOUND_ERROR_MESSAGE_NO_TASK);
		} else if (size == 1) {
			feedback = String
					.format(INDEX_OUT_OF_BOUND_ERROR_MESSAGE_SINGULAR_TASK);
		} else {
			feedback = String.format(
					INDEX_OUT_OF_BOUND_ERROR_MESSAGE_PLURAL_TASK, size);
		}

		return feedback;
	}
	
	private FILTER_TYPE determineFilterType(String filter) throws Exception {
		switch(filter.toUpperCase()) {
			case ALL_FILTER:
				return FILTER_TYPE.ALL;
			case PENDING_FILTER:
				return FILTER_TYPE.PENDING;
			case COMPLETE_FILTER:
				return FILTER_TYPE.COMPLETE;
			case OVERDUE_FILTER:
				return FILTER_TYPE.OVERDUE;
			default:
				throw new Exception("The filter keyword is not recognized");
		}
	}
	
}